/* (c) 2013 Fyren, see license.txt */

#include "whatiah.h"

std::mt19937 *mt;
std::map<CharmID, Charm*> charms;
std::map<MobID, Mob*> mobs;

void initWhatiah()
{
	std::random_device rd;
	mt = new std::mt19937(rd());
	initCharms();
	initMobs();
}

static void initCharms()
{
	Charm *c;
#define addCharm(e, s, st) \
	c = new Charm(e, s); \
	c->st; \
	charms[c->id] = c

	addCharm(woodenSword, "Wooden Sword", pDam(5)->pPen(20));
	addCharm(ratFlail, "Rat Flail", pDam(9));
	addCharm(largeBone, "Large Bone", pDam(6));
	addCharm(sparkWand, "Spark Wand", mDam(7)->mPen(20));
	addCharm(smallIceWand, "Small Ice Wand", mDam(4)->mPen(25));
	addCharm(bronzeHelm, "Bronze Helm", arm(11));
	addCharm(leatherPants, "Leather Pants", arm(9));
	addCharm(wornLeatherChest, "Worn Leather Chest", arm(8));
	addCharm(shoulderGlyph, "Shoulder Glyph", ward(11));
	addCharm(glyphedGloves, "Glyphed Gloves", ward(9));
	addCharm(smallGlyph, "Small Glyph", ward(8));
	addCharm(ratCloak, "Rat Cloak", arm(6)->ward(4));
	addCharm(goblinPunch, "Goblin Punch", pDam(10));
	addCharm(lightDust, "Light Dust", mDam(5)->mPen(80));
	addCharm(shell, "Shell", arm(5)->ward(5));
	addCharm(sparkles, "Sparkles", mDam(8)->mPen(25));
	addCharm(pairOfStoneSwords, "Pair of Stone Swords", pDam(6)->pPen(45));
	addCharm(heatWand, "Heat Wand", mDam(7));
	addCharm(darknessWand, "Darkness Wand", mDam(6));
	addCharm(smallFlameWand, "Small Flame Wand", mDam(6));
	addCharm(frostWand, "Frost Wand", mDam(5)->mPen(38));
}

//TODO: allow multiple mob instances of the same type
//TODO: convenience macros
//TODO: convenience something for multiple cards?
static void initMobs()
{
	Mob *m;
	
	Deck d =
        {
                charms[goblinPunch],
                charms[goblinPunch],
                charms[goblinPunch],
                charms[goblinPunch],
                charms[goblinPunch],
                charms[goblinPunch],
                charms[goblinPunch],
                charms[bronzeHelm],
                charms[bronzeHelm],
                charms[bronzeHelm],
                charms[bronzeHelm],
                charms[ratFlail],
                charms[ratFlail],
                charms[ratFlail],
                charms[ratFlail],
                charms[ratFlail],
        };

        m = new Mob("Old goblin", 43, d);
	mobs[oldGoblin] = m;

	d =
        {
                charms[lightDust],
                charms[lightDust],
                charms[lightDust],
                charms[lightDust],
                charms[lightDust],
                charms[lightDust],
                charms[lightDust],
                charms[shell],
                charms[shell],
                charms[shell],
                charms[shell],
                charms[sparkles],
                charms[sparkles],
                charms[sparkles],
                charms[sparkles],
                charms[sparkles],
                charms[sparkles],
        };

        m = new Mob("Prairie sprite", 35, d);
	mobs[prairieSprite] = m;
}

Charm::Charm(CharmID id_ /* = noCharm */, const char* n /* = NULL */) : 
	id(id_), a(0), w(0), cA(0), cW(0), 
	pD(0), pP(0), mD(0), mP(0), name(n) {}

Charm* Charm::arm(int a_) 
{ 
	a = a_; 
	return this; 
}

Charm* Charm::ward(int w_) 
{ 
	w = w_; 
	return this; 
}

Charm* Charm::cArm(int cA_) 
{ 
	cA = cA_; 
	return this; 
}

Charm* Charm::cWard(int cW_) 
{ 
	cW = cW_; 
	return this; 
}

Charm* Charm::pDam(int pD_)
{ 
	pD = pD_; 
	return this; 
}

Charm* Charm::pPen(int pP_) 
{ 
	if (pP_ > 100) 
	{ 
		std::cout << id << " (" << name << ") pPen: " << pP_ << std::endl; 
		exit(0);
	} 
	pP = pP_; 
	return this; 
}

Charm* Charm::mDam(int mD_) 
{ 
	mD = mD_;
	return this; 
}

Charm* Charm::mPen(int mP_) 
{ 
	if (mP_ > 100) 
	{ 
		std::cout << id << " (" << name << ") mPen: " << mP_ << std::endl; 
		exit(0);
	} 
	mP = mP_; 
	return this;
}

std::ostream& operator<<(std::ostream &out, Charm &ch)
{
	out << ch.name;
	return out;
}

Mob::Mob(const char *n, int HP_, Deck d, bool shuffle_ /* = true */) : name(n), maxHP(HP_), deck(d), a(0), w(0) 
{ 
	init(shuffle_); 
}

void Mob::init(bool shuffle_ /* = true */)
{
	alive = true;
	HP = maxHP;
	maxSP = deck.size();
	SP = maxSP;

	if (shuffle_) shuffle();
	it = deck.begin();
}

Charm* Mob::draw()
{
	if (it == deck.end()) 
	{
		std::cout << name << "'s deck was empty." << std::endl;
		exit(0);
	}
	SP--;
	return *it++;
}

bool Mob::checkAlive() { return alive = HP > 0 && SP > 0; }

void Mob::apply(Charm *ch)
{
	//TODO: does anything have both armor and cumulative armor? is this how it works?
	if (ch->a > a) a = ch->a;
	a += ch->cA;
	if (ch->w > w) w = ch->w;
	a += ch->cW;
	
	checkAlive();
}

void Mob::inflict(Charm *ch)
{
	int absorbed = a < ch->pD ? a : ch->pD;
	a -= absorbed;
	HP -= ch->pD - absorbed + floor(absorbed * ch->pP / (float) 100);

	absorbed = w < ch->mD ? w : ch->mD;
	w -= absorbed;
	HP -= ch->mD - absorbed + floor(absorbed * ch->mP / (float) 100);	

	checkAlive();
}

void Mob::shuffle() 
{ 
	std::shuffle(deck.begin(), deck.end(), *mt); 
}

std::ostream& operator<<(std::ostream &out, Mob &m)
{
	out << m.name << " (" << m.HP << "/" << m.maxHP << " HP, " << m.SP << "/" << m.maxSP << " SP, " << m.a << " A, " << m.w << " W)";
	return out;
}

Fight::Fight(Mobs t1, Mobs t2) : team1(t1), team2(t2) {}

void Fight::initMobs()
{
	std::for_each(team1.begin(), team1.end(), [](Mob *m) { m->init(); });
	std::for_each(team2.begin(), team2.end(), [](Mob *m) { m->init(); });
}

bool Fight::teamsAlive() 
{ 
	auto anyoneAlive = [](bool acc, Mob *m) { return acc || m->alive; };
	return std::accumulate(team1.begin(), team1.end(), false, anyoneAlive) 
		&& std::accumulate(team2.begin(), team2.end(), false, anyoneAlive); 
}

Mobs Fight::getTeam(Mob *m)
{
	auto it = std::find(team1.begin(), team1.end(), m);
	if (it == team1.end()) return team2;
	else return team1;
}

Mobs Fight::getOtherTeam(Mob *m)
{
	auto it = std::find(team1.begin(), team1.end(), m);
	if (it == team1.end()) return team1;
	else return team2;
}

Mob* Fight::getRandomMob(Mobs t)
{
	std::uniform_int_distribution<> dis(0, t.size() - 1);
	return t[dis(*mt)];
}

void Fight::nextMob(Mobs::iterator begin, Mobs::iterator end, Mobs::iterator &it)
{
	if (!teamsAlive()) return;

	if (it == end) 
	{
		it = begin;	
		return;
	}

	do
	{
		it++;
		if (it == end) it = begin;
	} while (!(*it)->alive);
}

void Fight::start(bool print)
{
	//TODO: no idea how turn order works
	Mobs mobs = {team1.front(), team2.front()};
	auto it = mobs.end();
	turn = 0;
	do
	{
		nextMob(mobs.begin(), mobs.end(), it);
		Mob *m = *it;
		turn++;
		if (print) std::cout << "Turn " << turn << std::endl;

		Charm *ch = m->draw();

		if (print) std::cout << m->name << " plays " << *ch << std::endl;

		m->apply(ch);
		//TODO: no idea how targeting works
		Mob *t = getRandomMob(getOtherTeam(m));
		t->inflict(ch);

		if (print) 
		{
			std::cout << *m << std::endl;
			std::cout << *t << std::endl;
		}
	} while (teamsAlive());

}
